//=============================================================================
//
//   Copyright (C) 2020 All rights reserved.
//
//   Filename:		macros.rs
//
//   Author: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Date: 2020-10-09 11:53
//
//   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Last modified:	2020-11-19 13:43
//
//   Description:
//
//=============================================================================
#![macro_use]

///
///
///
macro_rules! unwrap_all {
    ($($e:expr);+) => {{
        $($e.unwrap();)+
    }}
}

///
/// indexable impl
/// the impl type must as least
///     1. has a usize field named "id"
///
macro_rules! indexable_impl {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)?),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? Indexable for $struct$(<$($DimIdent),*>)?{
            /// get index field
            #[inline]
            fn get_index(&self) -> Option<usize> {
                self.id
            }
            /// set index field
            #[inline]
            fn set_index(&mut self, rhs: usize) {
                self.id = Some(rhs);
            }
        }
    };
}

///
/// integratable impl
/// the impl type must as least
///     1. has a generic type <DT:DimType>
///     2. has a life time 'a
///     3. has a member field &Vec<GaussPoint<'a, DT>> named "gp_list" containing gauss point list
///
macro_rules! integratable_impl {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)? ),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? Integratable<DT> for $struct$(<$($DimIdent),*>)?{
            /// return gp list
            #[inline]
            fn get_gp_list(&self) -> &Vec<GaussPoint<DT>> {
                &self.gp_list
            }
            #[inline]
            fn get_gp_list_mut(&mut self) -> &mut Vec<GaussPoint<DT>> {
                &mut self.gp_list
            }
        }
    };
}

///
/// gauss point like impl
/// the impl type must as least
///     1. has a generic type <DT:DimType>
///     2. has a Real field named "weight"
///
macro_rules! gauss_point_like_impl {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)? ),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? GaussPointLike<DT> for $struct$(<$($DimIdent),*>)?{
            /// return weight of current gauss point
            #[inline]
            fn get_weight(&self) -> Real {
                self.weight
            }
            /// change weight of current gauss point
            #[inline]
            fn set_weight(&mut self, rhs: Real) {
                self.weight = rhs;
            }
        }
    };
}

/////
///// has material impl
///// the impl type must as least
/////     1. has a dyn MaterialLike reference field named "mat"
/////
//macro_rules! has_material_impl {
//    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)? ),*>)?) => {
//        impl $(<$($DimIdent $(:$DimBound)?),*>)? HasMaterial<DT> for $struct$(<$($DimIdent),*>)?{
//            /// get material reference belongs to self
//            fn get_material(&self) -> Rc<dyn MaterialLike<DT>> {
//                match &self.mat {
//                    None => panic!("this object doesn't link a material yet!"),
//                    Some(ptr)=>ptr.upgrade().expect("Failed to upgrade material weak point")
//                }
//            }
//            /// set material reference in self
//            fn set_material(&mut self, rhs: Weak<dyn MaterialLike<DT>>) {
//                self.mat = Some(rhs);
//            }
//        }
//    };
//}

///
/// shape interface impl
/// the impl type must as least
///     1. has a Option<Rc<dyn PointPoolLike>> field member named "pool"
///
macro_rules! link_to_point_pool_impl {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)? ),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? Linked2PointPool<DT> for $struct$(<$($DimIdent),*>)?{
            /// get linked point pool
            fn get_point_pool(&self)->&dyn PointPoolLike<DT>{
                match &self.pool{
                    Some(p)=>p.as_ref(),
                    None=>panic!("The Object hasn't linked to a point pool"),
                }
            }
            /// set linked point pool
            fn set_point_pool(&mut self, rhs: Rc<dyn PointPoolLike<DT>>){
                 self.pool = Some(rhs);
            }
        }
    }
}

///
/// shape interface impl
/// the impl type must as least
///     1. has a generic type <DT:DimType>
///     2. has a Vec<&'a Point<DT>> field named "mat"
///     3. has a String field name "hash_key"
///
macro_rules! shape_interface_impl {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)? ),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? ShapeLike<DT> for $struct$(<$($DimIdent),*>)?{
            /// get point list
            #[inline]
            fn get_point_indices(&self) -> &[usize]{
                &self.pt_list
            }
            /// get mut point list
            #[inline]
            fn get_point_indices_mut(&mut self) -> &mut [usize]{
                &mut self.pt_list
            }
        }
        impl $(<$($DimIdent $(:$DimBound)?),*>)? Hashable<String> for $struct$(<$($DimIdent),*>)?{

            /// get hash key as &str
            #[inline]
            fn get_hashkey(&self) -> String{
                let mut list = self.get_point_indices().to_vec();
                list.sort();
                match serde_json::to_string(&list) {
                    Err(error) => panic!("Problem parsing point list to string: {:?}", error),
                    Ok(v) => v,
                }
            }
            /// set hash key
            #[inline]
            fn set_hashkey(&mut self, _rhs: String) {
                unimplemented!()
            }
            /// update hash key
            #[inline]
            fn update_hashkey(&mut self) {
                unimplemented!()
            }
        }
        impl $(<$($DimIdent $(:$DimBound)?),*>)? PartialEq for $struct$(<$($DimIdent),*>)?{
            fn eq(&self,rhs:&Self) -> bool {
                self.get_hashkey() == rhs.get_hashkey()
            }
        }
        impl $(<$($DimIdent $(:$DimBound)?),*>)? std::hash::Hash for $struct$(<$($DimIdent),*>)?{
            fn hash<H: std::hash::Hasher>(&self, state: &mut H){
                self.get_hashkey().hash(state);
            }
        }
        impl $(<$($DimIdent $(:$DimBound)?),*>)? PointPoolLike<DT> for $struct$(<$($DimIdent),*>)?{
            fn append_point(&mut self, rhs: Point<DT::Dim>) -> &Point<DT::Dim>{
                unimplemented!()
            }
            fn get_point(&self, index: usize) -> &Point<DT::Dim>{
                let global_index = self.get_point_index(index as usize);
                self.get_point_pool().get_point(global_index)
            }
            fn get_point_num(&self) -> usize{
                self.pt_list.len()
            }
        }
        indexable_impl!($struct$(<$($DimIdent $(:$DimBound)?),*>)?);
        link_to_point_pool_impl!($struct$(<$($DimIdent $(:$DimBound)?),*>)?);
    }
}

///
/// material impl
///
macro_rules! material_parameter_impl {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)? ),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? MaterialCommonParameter for $struct$(<$($DimIdent),*>)?{
            #[inline]
            fn get_volume_weight(&self)->Real{
                self.volume_weight
            }
            #[inline]
            fn get_friction_angle(&self) -> Real{self.friction_angle}
            #[inline]
            fn get_dilation_angle(&self) -> Real{self.dilation_angle}
            #[inline]
            fn get_cohesion(&self) -> Real{self.cohesion}
            #[inline]
            fn get_permeability(&self) -> nalgebra::Vector3::<Real>{self.perm}
            #[inline]
            fn get_zeta(&self) -> Real{self.zeta}
            #[inline]
            fn get_k_ic(&self) -> Real{self.k_ic}
            #[inline]
            fn get_elastic_modulus(&self) -> Real{self.elastic_modulus}
            #[inline]
            fn get_elastic_poisson(&self) -> Real{self.elastic_poisson}
        }
    indexable_impl!($struct$(<$($DimIdent $(:$DimBound)?),*>)?);
    has_name_impl!($struct$(<$($DimIdent $(:$DimBound)?),*>)?);
    };
}

///
/// step like impl
///
macro_rules! step_like_impl {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)? ),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? StepLike for $struct$(<$($DimIdent),*>)?{
            #[inline]
            fn get_stime(&self) -> Real{
                self.stime
            }
            #[inline]
            fn get_etime(&self) -> Real{
                self.etime
            }
            #[inline]
            fn set_stime(&mut self,rhs:Real){
                self.stime=rhs;
            }
            #[inline]
            fn set_etime(&mut self,rhs:Real){
                self.etime=rhs;
            }
        }
        impl $(<$($DimIdent $(:$DimBound)?),*>)? std::fmt::Display for $struct$(<$($DimIdent),*>)?{
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(Time period from {} to {})", self.stime, self.etime)
            }
        }
    };
}

///
/// element like impl
/// the impl type must as least
///     1. satisfy indexable_impl
///     2. satisfy integratable_impl
///     3. satisfy shape_interface_impl
///     4. satisfy has_material_impl
///
macro_rules! element_like_impl {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)? ),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? ElementLike<DT> for $struct$(<$($DimIdent),*>)?{
            /// constructor with point list
            fn from_plist(pt_list: Vec<Weak<Point<DT::Dim>>>) -> Self {
                let mut shp = Self {
                    id: None,
                    pt_list,
                    ..Default::default()
                };
                shp.update_hashkey();
                shp
            }

            fn get_connectivity(&self) -> &[HashMap<String,Weak<dyn ElementLike<DT>>>; 4]{
                &self.mesh
            }
            fn get_connectivity_mut(&mut self) -> &mut [HashMap<String,Weak<dyn ElementLike<DT>>>; 4]{
                &mut self.mesh
            }
        }
        integratable_impl!($struct$(<$($DimIdent $(:$DimBound)?),*>)?);
        shape_interface_impl!($struct$(<$($DimIdent $(:$DimBound)?),*>)?);
        has_material_impl!($struct$(<$($DimIdent $(:$DimBound)?),*>)?);
    };
}

///
/// get type name
///
macro_rules! has_name_impl {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)? ),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? HasName for $struct$(<$($DimIdent),*>)?{
            fn type_name(&self) -> String{
                stringify!($struct).to_string()
            }
            fn name() -> String{
                stringify!($struct).to_string()
            }
        }
    };
}

macro_rules! default_dt_marker {
    ($struct:ident $(<$($DimIdent:ident $(:$DimBound:ident)?),*>)?) => {
        impl $(<$($DimIdent $(:$DimBound)?),*>)? Default for $struct$(<$($DimIdent),*>)?{
            fn default() -> Self {
                Self {
                    _marker: PhantomData::<DT> {},
                }
            }
        }
    };
}
